package me.aartikov.alligator.navigators;


import java.util.List;

import me.aartikov.alligator.util.Log;
import me.aartikov.alligator.animations.AnimationData;
import me.aartikov.alligator.animations.TransitionAnimation;
import me.aartikov.alligator.exceptions.NavigationException;
import me.aartikov.alligator.exceptions.ScreenNotFoundException;
import me.aartikov.alligator.exceptions.ScreenRegistrationException;
import me.aartikov.alligator.helpers.FractionStack;
import me.aartikov.alligator.listeners.ScreenResultListener;
import me.aartikov.alligator.listeners.TransitionListener;
import me.aartikov.alligator.Screen;
import me.aartikov.alligator.ScreenResult;
import me.aartikov.alligator.TransitionType;
import me.aartikov.alligator.animations.providers.TransitionAnimationProvider;
import me.aartikov.alligator.destinations.FragmentDestination;
import me.aartikov.alligator.helpers.DialogFraction;
import me.aartikov.alligator.helpers.MiFraction;
import me.aartikov.alligator.helpers.ScreenResultHelper;
import me.aartikov.alligator.navigationfactories.NavigationFactory;
import me.aartikov.alligator.DestinationType;
import ohos.aafwk.ability.fraction.FractionManager;

public class DefaultFragmentNavigator implements FragmentNavigator {

    private boolean mFlowNavigation;

    private FractionStack mFragmentStack;


    private NavigationFactory mNavigationFactory;


    private ScreenResultHelper mScreenResultHelper;


    private TransitionListener mTransitionListener;


    private ScreenResultListener mScreenResultListener;




    private TransitionAnimationProvider mAnimationProvider;

    public DefaultFragmentNavigator(boolean flowNavigation,
                                    FractionManager fragmentManager,
                                    NavigationFactory navigationFactory,
                                    TransitionListener transitionListener,
                                    ScreenResultListener screenResultListener,
                                    TransitionAnimationProvider animationProvider) {
        mFlowNavigation = flowNavigation;
        mFragmentStack = FractionStack.getInstance(fragmentManager);
        mNavigationFactory = navigationFactory;
        mScreenResultHelper = new ScreenResultHelper(mNavigationFactory);
        mTransitionListener = transitionListener;
        mScreenResultListener = screenResultListener;
        mAnimationProvider = animationProvider;
    }

    @Override
    public boolean goForward(Screen screen,
                             FragmentDestination destination,
                             AnimationData animationData) throws NavigationException {

        MiFraction currentFragment = mFragmentStack.getCurrentFragment();
        Class<? extends Screen> screenClassFrom = currentFragment == null ? null : mNavigationFactory.getScreenClass(currentFragment);
        Class<? extends Screen> screenClassTo = screen.getClass();

        MiFraction fragment = destination.createFragment(screen);

        if (fragment instanceof DialogFraction) {
            throw new ScreenRegistrationException("DialogFragment is used as usual Fragment.");
        }
        TransitionAnimation animation = getAnimation(TransitionType.FORWARD, screenClassFrom, screenClassTo, animationData);
        boolean isSuc = mFragmentStack.push(fragment, animation);
        callTransitionListener(TransitionType.FORWARD, screenClassFrom, screenClassTo);
        return isSuc;
    }


    @Override
    public boolean replace(Screen screen,
                           FragmentDestination destination,
                           AnimationData animationData) throws NavigationException {
        Log.i("DefaultFragmentNavigator.replace([screen, destination, animationData]):93");
        MiFraction fragment = destination.createFragment(screen);
        MiFraction currentFragment = mFragmentStack.getCurrentFragment();

        Class<? extends Screen> screenClassFrom = currentFragment == null ? null : mNavigationFactory.getScreenClass(currentFragment);
        Class<? extends Screen> screenClassTo = screen.getClass();
        TransitionAnimation animation = getAnimation(TransitionType.REPLACE, screenClassFrom, screenClassTo, animationData);

        boolean isSuc = mFragmentStack.replace(fragment, animation);
        callTransitionListener(TransitionType.REPLACE, screenClassFrom, screenClassTo);
        return isSuc;
    }

    @Override
    public boolean reset(Screen screen,
                         FragmentDestination destination,
                         AnimationData animationData) throws NavigationException {
        Log.i("DefaultFragmentNavigator.reset([screen, destination, animationData]):112");
        MiFraction fragment = destination.createFragment(screen);
        MiFraction currentFragment = mFragmentStack.getCurrentFragment();

        Class<? extends Screen> screenClassFrom = currentFragment == null ? null : mNavigationFactory.getScreenClass(currentFragment);
        Class<? extends Screen> screenClassTo = screen.getClass();
        TransitionAnimation animation = getAnimation(TransitionType.RESET, screenClassFrom, screenClassTo, animationData);

        boolean isScu = mFragmentStack.reset(fragment, animation);
        callTransitionListener(TransitionType.RESET, screenClassFrom, screenClassTo);
        return isScu;
    }

    @Override
    public boolean canGoBack() {
        return mFragmentStack.getStackFragment().size() > 1;
    }

    @Override
    public boolean goBack(Screen screen, ScreenResult screenResult,
                          AnimationData animationData) throws NavigationException {

        List<MiFraction> fragments = mFragmentStack.getStackFragment();
        MiFraction currentFragment = fragments.get(fragments.size() - 1);
        MiFraction previousFragment = fragments.get(fragments.size() - 2);

        Class<? extends Screen> screenClassFrom = mNavigationFactory.getScreenClass(currentFragment);
        Class<? extends Screen> screenClassTo = mNavigationFactory.getScreenClass(previousFragment);


        TransitionAnimation animation = getAnimation(TransitionType.BACK, screenClassFrom, screenClassTo, animationData);
        boolean isSuc = mFragmentStack.pop(screen, animation);
        callTransitionListener(TransitionType.BACK, screenClassFrom, screenClassTo);
        mScreenResultHelper.callScreenResultListener(currentFragment, screenResult, mScreenResultListener);
        return isSuc;
    }

    @Override
    public boolean goBackTo(Class<? extends Screen> screenClass,
                            FragmentDestination destination,
                            ScreenResult screenResult,
                            AnimationData animationData) throws NavigationException {

        List<MiFraction> fragments = mFragmentStack.getStackFragment();
        MiFraction requiredFragment = null;
        boolean toPrevious = false;
        for (int i = fragments.size() - 1; i >= 0; i--) {
            if (screenClass == mNavigationFactory.getScreenClass(fragments.get(i))) {
                requiredFragment = fragments.get(i);
                toPrevious = i == fragments.size() - 2;
                break;
            }
        }

        if (requiredFragment == null) {
            throw new ScreenNotFoundException(screenClass);
        }

        MiFraction currentFragment = fragments.get(fragments.size() - 1);
        Class<? extends Screen> screenClassFrom = mNavigationFactory.getScreenClass(currentFragment);
        TransitionAnimation animation = getAnimation(TransitionType.BACK, screenClassFrom, screenClass, animationData);

        boolean isSuc = mFragmentStack.popUntil(requiredFragment, animation);
        callTransitionListener(TransitionType.BACK, screenClassFrom, screenClass);
        if (screenResult != null || toPrevious) {
            mScreenResultHelper.callScreenResultListener(currentFragment, screenResult, mScreenResultListener);
        }
        return isSuc;
    }


    private TransitionAnimation getAnimation(TransitionType transitionType,
                                             Class<? extends Screen> screenClassFrom,
                                             Class<? extends Screen> screenClassTo,
                                             AnimationData animationData) {
        if (screenClassFrom == null || screenClassTo == null) {
            return TransitionAnimation.DEFAULT;
        } else {
            DestinationType destinationType = mFlowNavigation ? DestinationType.FLOW_FRAGMENT : DestinationType.FRAGMENT;
            return mAnimationProvider.getAnimation(transitionType, destinationType, screenClassFrom, screenClassTo, animationData);
        }
    }

    private void callTransitionListener(TransitionType transitionType,
                                        Class<? extends Screen> screenClassFrom,
                                        Class<? extends Screen> screenClassTo) {
        DestinationType destinationType = mFlowNavigation ? DestinationType.FLOW_FRAGMENT : DestinationType.FRAGMENT;
        mTransitionListener.onScreenTransition(transitionType, destinationType, screenClassFrom, screenClassTo);
    }
}
